#include "webglk.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

static void unicode2utf8(glui32 ch);

static stream_t streams[100];
static strid_t curStream=0,prevStream=0;

extern uint8_t *rom;

static char *buffer;
static int bufferpos;

static strid_t newStream(int type,int readable,int writable,glui32 rock)
{
	strid_t str;
	for (str=1;str<100;str++)
		if (!streams[str].inuse)
			break;
	if (str==100) return 0;

	streams[str].inuse=1;
	streams[str].type=type;
	streams[str].rock=rock;
	streams[str].readable=readable;
	streams[str].writable=writable;
	streams[str].readcount=0;
	streams[str].writecount=0;
	streams[str].unicode=0;
	return str;
}

strid_t glk_stream_new_window(winid_t win)
{
	strid_t str=newStream(strtype_Window,0,1,0);
	if (str) streams[str].win=win;
	return str;
}

/*
We totally cheat here, since the pointer passed will change in memory.
So we accept a pointer, but we only store its offset from rom.
This won't work for any memory not inside rom, which will be OK for us.
*/
strid_t glk_stream_open_memory(char *buf,glui32 buflen,glui32 fmode,glui32 rock)
{
	strid_t str=newStream(strtype_Memory,fmode&filemode_Read,fmode&filemode_Write,rock);
	if (str)
	{
		streams[str].buf=((uint8_t *)buf)-rom;
		streams[str].ptr=streams[str].buf;
		streams[str].endbuf=streams[str].buf+buflen;
	}
	return str;
}
strid_t glk_stream_open_memory_uni(glui32 *buf,glui32 buflen,glui32 fmode,glui32 rock)
{
	strid_t str=newStream(strtype_Memory,fmode&filemode_Read,fmode&filemode_Write,rock);
	if (str)
	{
		streams[str].unicode=1;
		streams[str].buf=buf-(glui32*)rom;
		streams[str].ptr=streams[str].buf;
		streams[str].endbuf=streams[str].buf+buflen*4;
	}
	return str;
}
strid_t glk_stream_open_file(frefid_t fileref,glui32 fmode,glui32 rock)
{
	strid_t str=newStream(strtype_File,fmode&filemode_Read,fmode&filemode_Write,rock);
	return str;
}
void glk_stream_close(strid_t str,stream_result_t *result)
{
	if (str==curStream)
	{
		curStream=0;
		if (streams[str].type==strtype_Window) prevStream=0;
	}
	if (result)
	{
		result->readcount=streams[str].readcount;
		result->writecount=streams[str].writecount;
	}
	streams[str].inuse=0;
}
void glk_stream_set_current(strid_t str)
{
	if (streams[str].type==strtype_Window && str!=prevStream)
	{
		glk_stream_buffer_flush();
		printf("{m:'setstream',s:%u},\n",streams[str].win);
		prevStream=str;
	}
	curStream=str;
}
void glk_stream_set_position(strid_t str,glsi32 pos,glui32 seekmode)
{
	// we don't read files, we certainly don't write to them..
	if (streams[str].type==strtype_File) return;
	if (streams[str].type!=strtype_Memory) fatal("Can't seek this stream");
	if (seekmode==seekmode_Start) streams[str].ptr=streams[str].buf+pos;
	if (seekmode==seekmode_Current) streams[str].ptr+=pos;
	if (seekmode==seekmode_End) streams[str].ptr=streams[str].endbuf+pos;
}
strid_t glk_stream_get_current()
{
	return curStream;
}

strid_t glk_stream_iterate(strid_t str,glui32 *rock)
{
	strid_t i;
	for (i=str+1;i<100;i++)
	{
		if (streams[i].inuse)
		{
			if (rock) *rock=streams[i].rock;
			return i;
		}
	}
	if (rock) *rock=0;
	return 0;
}

void glk_put_char_stream(strid_t str,unsigned char ch)
{
	strid_t oldCS=curStream;
	glk_stream_set_current(str);
	glk_put_char(ch);
	glk_stream_set_current(oldCS);
}
void glk_put_char(unsigned char ch)
{
	glk_put_char_uni(ch);
}
void glk_put_char_uni(glui32 ch)
{
	if (!curStream) return;

	if ((ch=='\n' || ch=='\r') && streams[curStream].type==strtype_Window)
	{
		glk_stream_buffer_flush();
		printf("{m:'newline'},\n");
	}
	else
	{
		if (streams[curStream].type==strtype_Window)
		{
			if (ch=='\'' || ch=='\\') buffer[bufferpos++]='\\';
			unicode2utf8(ch);
		}
		else if (streams[curStream].type==strtype_Memory)
		{
			if (streams[curStream].ptr<streams[curStream].endbuf)
			{
				if (streams[curStream].unicode)
				{
					rom[streams[curStream].ptr++]=ch>>24;
					rom[streams[curStream].ptr++]=ch>>16;
					rom[streams[curStream].ptr++]=ch>>8;
				}
				rom[streams[curStream].ptr++]=ch;
			}
			streams[curStream].writecount++;
		}
		else if (streams[curStream].type==strtype_File)
		{
			// I am so not writing to a file.
			streams[curStream].writecount++;
		}
		else fatal("invalid stream type");
	}
	if (bufferpos>8000) glk_stream_buffer_flush();
}
void glk_put_string(char *s)
{
	while (*s) glk_put_char(*s++);
}


void glk_stream_buffer_open()
{
	buffer=malloc(8192);
	bufferpos=0;
}
void glk_stream_buffer_flush()
{
        if (bufferpos)
        {
                buffer[bufferpos]=0;
                printf("{c:'%s'},\n",buffer);
                bufferpos=0;
        }
}
void glk_stream_buffer_close()
{
	free(buffer);
}

void glk_stream_load(FILE *f)
{
	fread(streams,sizeof(streams),1,f);
	fread(&curStream,sizeof(curStream),1,f);
	fread(&prevStream,sizeof(curStream),1,f);
}
void glk_stream_save(FILE *f)
{
	fwrite(streams,sizeof(streams),1,f);
	fwrite(&curStream,sizeof(curStream),1,f);
	fwrite(&prevStream,sizeof(prevStream),1,f);
}

static void unicode2utf8(glui32 ch)
{
	if (ch<0x80) buffer[bufferpos++]=ch;
	else if (ch<0x800)
	{
		buffer[bufferpos++]=0xc0|(ch>>6);
		buffer[bufferpos++]=0x80|(ch&0x3f);
	}
	else if (ch<0x10000)
	{
		buffer[bufferpos++]=0xe0|(ch>>12);
		buffer[bufferpos++]=0x80|((ch>>6)&0x3f);
		buffer[bufferpos++]=0x80|(ch&0x3f);
	}
	else if (ch<0x200000)
	{
		buffer[bufferpos++]=0xf0|(ch>>18);
		buffer[bufferpos++]=0x80|((ch>>12)&0x3f);
		buffer[bufferpos++]=0x80|((ch>>6)&0x3f);
		buffer[bufferpos++]=0x80|(ch&0x3f);
	}
	else if (ch<0x4000000)
	{
		buffer[bufferpos++]=0xf8|(ch>>24);
		buffer[bufferpos++]=0x80|((ch>>18)&0x3f);
		buffer[bufferpos++]=0x80|((ch>>12)&0x3f);
		buffer[bufferpos++]=0x80|((ch>>6)&0x3f);
		buffer[bufferpos++]=0x80|(ch&0x3f);
	}
	else
	{
		buffer[bufferpos++]=0xfc|(ch>>30);
		buffer[bufferpos++]=0x80|((ch>>24)&0x3f);
		buffer[bufferpos++]=0x80|((ch>>18)&0x3f);
		buffer[bufferpos++]=0x80|((ch>>12)&0x3f);
		buffer[bufferpos++]=0x80|((ch>>6)&0x3f);
		buffer[bufferpos++]=0x80|(ch&0x3f);
	}
}
